﻿/*
    Copyright 2010, 2011 Eric Wong
	contact@optimalcycling.com
	http://www.optimalcycling.com
	Optimal Cycling - Advanced power pacing program for cyclists
	
	This file is part of Optimal Cycling.

    Optimal Cycling is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Optimal Cycling is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Optimal Cycling.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using OptimalCycling.FileIO;
using OptimalCycling.FileIO.OC;
using OptimalCycling.Interop;
using CommandLineHelper;
using Cloo;


namespace OptimalCycling
{
    #region Alias

    using sizeT = System.UInt32;
    using size_array = System.UInt32;

    // Allow us to easily switch between floats and double values
#if REAL_IS_DOUBLE
    using real = System.Double;
    using real2 = Vector2d;
    using real4 = Vector4d;
    using real8 = Vector8d;
#else
    using real = System.Single;
    using real2 = Vector2;
    using real4 = Vector4;
    using real8 = Vector8;
#endif

    #endregion

    public partial class OCSolve
    {
        private class PtrHolder
        {
            public IntPtr powerValAPtr, powerValBPtr, powerLimitsPtr, fitnessPtr, correspondingIndexPtr, bestFitIndexPtr, coursePosPointsPtr,
                coursePosLinearPtr, coursePosDirectionPtr, coursePosInfoPtr, riderPropPtr, windPtr, climatePtr, dragCoeffWheelYawAnglesPtr,
                dragCoeffWheelsPtr, modelInfoPtr, iConstantsPtr, randNumGenSeedsPtr, solverInfoPtr, solverVarsPtr, powerMetricsInfoPtr,
                powerMetricsCriticalsPtr, tempWorkingSpacePtr, bestTimePtr, bestPowerResultsPtr, bestKinematicsPtr, fitStatsPtr;

            public List<GCHandle> gcHandles = new List<GCHandle>();
        }

        private void OCSimRun(PtrHolder ptrHolder, int currSolutionMode, int whichArray)
        {
            if (IntPtr.Size >= 8)   // 64-bit native
            {
                Interop.OCCLibX64.OCSimRun(currSolutionMode, whichArray, numIndividuals, numSimPoints, ptrHolder.powerValAPtr, ptrHolder.powerValBPtr,
                    ptrHolder.powerLimitsPtr, powerAllSame, ptrHolder.fitnessPtr, ptrHolder.correspondingIndexPtr, ptrHolder.bestFitIndexPtr,
                    numIndividPerPop, numPopulations, ptrHolder.coursePosPointsPtr, ptrHolder.coursePosLinearPtr, coursePosLinearMax,
                    ptrHolder.coursePosDirectionPtr, ptrHolder.coursePosInfoPtr, ptrHolder.riderPropPtr, ptrHolder.windPtr, ptrHolder.climatePtr,
                    ptrHolder.dragCoeffWheelYawAnglesPtr, ptrHolder.dragCoeffWheelsPtr, dragCoeffWheelsNumPts, ptrHolder.modelInfoPtr,
                    ptrHolder.iConstantsPtr, ptrHolder.randNumGenSeedsPtr, ptrHolder.solverInfoPtr, ptrHolder.solverVarsPtr,
                    ptrHolder.powerMetricsInfoPtr, ptrHolder.powerMetricsCriticalsPtr, ptrHolder.tempWorkingSpacePtr, ptrHolder.bestTimePtr,
                    ptrHolder.bestPowerResultsPtr, ptrHolder.bestKinematicsPtr, ptrHolder.fitStatsPtr);
            }
            else   // 32-bit native
            {
                Interop.OCCLibX86.OCSimRun(currSolutionMode, whichArray, numIndividuals, numSimPoints, ptrHolder.powerValAPtr, ptrHolder.powerValBPtr,
                    ptrHolder.powerLimitsPtr, powerAllSame, ptrHolder.fitnessPtr, ptrHolder.correspondingIndexPtr, ptrHolder.bestFitIndexPtr,
                    numIndividPerPop, numPopulations, ptrHolder.coursePosPointsPtr, ptrHolder.coursePosLinearPtr, coursePosLinearMax,
                    ptrHolder.coursePosDirectionPtr, ptrHolder.coursePosInfoPtr, ptrHolder.riderPropPtr, ptrHolder.windPtr, ptrHolder.climatePtr,
                    ptrHolder.dragCoeffWheelYawAnglesPtr, ptrHolder.dragCoeffWheelsPtr, dragCoeffWheelsNumPts, ptrHolder.modelInfoPtr,
                    ptrHolder.iConstantsPtr, ptrHolder.randNumGenSeedsPtr, ptrHolder.solverInfoPtr, ptrHolder.solverVarsPtr,
                    ptrHolder.powerMetricsInfoPtr, ptrHolder.powerMetricsCriticalsPtr, ptrHolder.tempWorkingSpacePtr, ptrHolder.bestTimePtr,
                    ptrHolder.bestPowerResultsPtr, ptrHolder.bestKinematicsPtr, ptrHolder.fitStatsPtr);
            }
        }

        private void outProgressMsg(TimeSpan ts, ulong genNum, real bestTime, sizeT viableFitCount, sizeT totalPopulationNum, real aveFitness,
            StreamWriter consoleOutFileWriter, TextWriter stdOutTextWriter)
        {
            real convergence = Math.Abs(aveFitness - bestTime);

            string tsFormatted = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
            string progressMessage = tsFormatted.PadLeft(13)
                + "," + String.Format("{0,13}", genNum) + ","
                + String.Format("{0,15:0.0000}", bestTime) + "," + String.Format("{0,10}", viableFitCount) + ","
                + String.Format("{0,18:0.000000}", convergence);

            Console.SetOut(consoleOutFileWriter);
            Console.WriteLine(progressMessage);

            Console.SetOut(stdOutTextWriter);
            Console.WriteLine(progressMessage);
        }

        private double Optimize()
        {
            Stopwatch stopWatch = new Stopwatch();
            PtrHolder ptrHolder = (useNativeCPU ? new PtrHolder() : null);
            int kernelArgCount = 0; 

            #region Read Data

            if (FileOCDataV2_1.ReadOCData(out ocData, folderPath))
            {
                Console.SetOut(stdOutTextWriter);
                Console.WriteLine("Upgrading data file format to " + OptimalCyclingFileFormat.OptimalCyclingDataV2_1.ToString());
                Console.WriteLine("This version of Optimal Cycling or later is required to run the new format.");

                FileOCDataV2_1.WriteOCData(ocData, false, folderPath);
            }

            #endregion

            #region Num Points & Num Individuals

            OptimizationChoices powerOptChoice = (OptimizationChoices)Convert.ToInt32(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.powerOptimizationType.ColName].ToString());

            numSimPoints = (sizeT)ocData.Tables[DataFiles.course.TableName].Rows.Count;
            if ((size_array)numSimPoints < 2)
                throw new Exception(@"Too few course simulation points. Minimum 2.");

            if (powerOptChoice != OptimizationChoices.NoOptimization)
            {
                numIndividuals = DataConversion.ConvertTosizeT(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.populationSize.ColName].ToString());
            }
            else
                numIndividuals = (sizeT)1;

            #endregion

            #region Parse Changing Params / Load Intermediate State / Set Console Out File

            // Parse the data from the original data; Unknown if there are intermediates yet
            solveData = new OCSolveData();
            ParseSolveData();
            intermedABFlag = 1;

            // If the intermediate exists
            // Create the serializer
            XmlSerializer serializer = new XmlSerializer(typeof(OCSolveData));
            if (powerOptChoice != OptimizationChoices.NoOptimization)
            {
                if (!Directory.Exists(intermedFolderPath))
                {
                    Directory.CreateDirectory(intermedFolderPath);
                }
                else
                {
                    OCSolveData solveDataA = null;
                    OCSolveData solveDataB = null;
                    string[] filesInIntermedFolder = Directory.GetFiles(intermedFolderPath);

                    if (File.Exists(intermedA))
                    {
                        try
                        {
                            using (StreamReader streamReader = File.OpenText(intermedA))
                            {
                                solveDataA = serializer.Deserialize(streamReader) as OCSolveData;
                            }
                        }
                        catch (Exception)
                        {
                            solveDataA = null;
                        }
                    }

                    if (File.Exists(intermedB))
                    {
                        try
                        {
                            using (StreamReader streamReader = File.OpenText(intermedB))
                            {
                                solveDataB = serializer.Deserialize(streamReader) as OCSolveData;
                            }
                        }
                        catch (Exception)
                        {
                            solveDataB = null;
                        }
                    }

                    if (solveDataA != null && solveDataB != null)
                    {
                        if (solveDataA.solverVars[0].currGen >= solveDataB.solverVars[0].currGen)
                        {
                            solveData = solveDataA;
                            intermedABFlag = 0;
                        }
                        else
                        {
                            solveData = solveDataB;
                            intermedABFlag = 1;
                        }
                    }
                    else if (solveDataA != null)
                    {
                        solveData = solveDataA;
                        intermedABFlag = 0;
                    }
                    else if (solveDataB != null)
                    {
                        solveData = solveDataB;
                        intermedABFlag = 1;
                    }
                }
            }

            FileStream consoleOutFS = new FileStream(consoleOutFilePath, FileMode.Append, FileAccess.Write, System.IO.FileShare.ReadWrite);
            consoleOutFileWriter = new StreamWriter(consoleOutFS);

            #endregion

            #region Mode and Size

            int currSolutionModeIndex = 0, whichArrayIndex = 0, whichArray = 0;

            if (!useNativeCPU)
            {
                // SolutionMode currSolutionMode
                currSolutionModeIndex = kernelArgCount;
                kernel.SetValueArgument<int>(kernelArgCount, (int)SolutionMode.SolutionMode_Initialize);
                kernelArgCount++;

                // uint whichArrays
                whichArrayIndex = kernelArgCount;
                whichArray = 0;
                kernel.SetValueArgument<int>(kernelArgCount, whichArray);
                kernelArgCount++;

                // const sizeT numIndividuals
                kernel.SetValueArgument<sizeT>(kernelArgCount, numIndividuals);
                kernelArgCount++;

                // const sizeT numSimPoints
                kernel.SetValueArgument<sizeT>(kernelArgCount, numSimPoints);
                kernelArgCount++;
            }

            #endregion

            #region Power

            // __global real* powerValA, 
	        // __global real* powerValB,
            if (useNativeCPU)
            {
                GCHandle powerValAPtr = GCHandle.Alloc(solveData.powerValA, GCHandleType.Pinned);
                ptrHolder.powerValAPtr = powerValAPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(powerValAPtr);

                GCHandle powerValBPtr = GCHandle.Alloc(solveData.powerValB, GCHandleType.Pinned);
                ptrHolder.powerValBPtr = powerValBPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(powerValBPtr);
            }
            else
            {
                powerValABuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.powerValA);
                kernel.SetMemoryArgument(kernelArgCount, powerValABuffer, true);
                kernelArgCount++;

                powerValBBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.powerValB);
                kernel.SetMemoryArgument(kernelArgCount, powerValBBuffer, true);
                kernelArgCount++;
            }

            //__global const real2* powerLimits
            real2[] powerLimits = new real2[(size_array)numSimPoints];
            for (int j = 0; j < powerLimits.Length; j++)
            {
                powerLimits[j].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.powerMin.ColName].ToString());
                powerLimits[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.powerMax.ColName].ToString());
            }

            ComputeBuffer<real2> powerLimitsBuffer;
            if (useNativeCPU)
            {
                GCHandle powerLimitsPtr = GCHandle.Alloc(powerLimits, GCHandleType.Pinned);
                ptrHolder.powerLimitsPtr = powerLimitsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(powerLimitsPtr);
            }
            else
            {
                powerLimitsBuffer = new ComputeBuffer<real2>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, powerLimits);
                kernel.SetMemoryArgument(kernelArgCount, powerLimitsBuffer, true);
                kernelArgCount++;
            }


            // const uint powerAllSame
            if (useNativeCPU)
            {
                if (powerOptChoice == OptimizationChoices.OptimizeUsingLimitsAllSame)
                    powerAllSame = 1;
                else
                    powerAllSame = 0;
            }
            else
            {
                if (powerOptChoice == OptimizationChoices.OptimizeUsingLimitsAllSame)
                    powerAllSame = 1;
                else
                    powerAllSame = 0;

                kernel.SetValueArgument<int>(kernelArgCount, powerAllSame);
                kernelArgCount++;
            }

            #endregion

            #region Fitness + Population

            //__global real* fitness
            if (useNativeCPU)
            {
                GCHandle fitnessPtr = GCHandle.Alloc(solveData.fitness, GCHandleType.Pinned);
                ptrHolder.fitnessPtr = fitnessPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(fitnessPtr);
            }
            else
            {
                fitnessBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.fitness);
                kernel.SetMemoryArgument(kernelArgCount, fitnessBuffer, true);
                kernelArgCount++;
            }
        
            int correspondingIndexIndex = 0;

            if (useNativeCPU)
            {
                // __global sizeT* correspondingIndex
                GCHandle correspondingIndexPtr = GCHandle.Alloc(solveData.correspondingIndex, GCHandleType.Pinned);
                ptrHolder.correspondingIndexPtr = correspondingIndexPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(correspondingIndexPtr);

                // __global sizeT bestFitIndex
                GCHandle bestFitIndexPtr = GCHandle.Alloc(solveData.bestFitIndex, GCHandleType.Pinned);
                ptrHolder.bestFitIndexPtr = bestFitIndexPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(bestFitIndexPtr);
            }
            else
            {
                // __global sizeT* correspondingIndex
                correspondingIndexBuffer = new ComputeBuffer<sizeT>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.correspondingIndex);
                kernel.SetMemoryArgument(kernelArgCount, correspondingIndexBuffer, true);
                correspondingIndexIndex = kernelArgCount;
                kernelArgCount++;

                // __global sizeT bestFitIndex
                bestFitIndexBuffer = new ComputeBuffer<sizeT>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.bestFitIndex);
                kernel.SetMemoryArgument(kernelArgCount, bestFitIndexBuffer, true);
                kernelArgCount++;
            }

            numPopulations = DataConversion.ConvertTosizeT(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.numPopulations.ColName].ToString());
            numIndividPerPop = (sizeT)((size_array)numIndividuals / (size_array)numPopulations);
            if ((size_array)numIndividPerPop * (size_array)numPopulations != (size_array)numIndividuals)
                throw new Exception("Incompatible number of populations/individuals per population");

            if (!useNativeCPU)
            {
                //const sizeT numIndividPerPop
                kernel.SetValueArgument<sizeT>(kernelArgCount, numIndividPerPop);
                kernelArgCount++;

                //const sizeT numPopulations
                kernel.SetValueArgument<sizeT>(kernelArgCount, numPopulations);
                kernelArgCount++;
            }

            #endregion

            #region Course Points

            //__global const real4* coursePosPoints
            real4[] coursePosPoints = new real4[(size_array)numSimPoints];
            for (int j = 0; j < coursePosPoints.Length; j++)
            {
                coursePosPoints[j].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.courseX.ColName].ToString());
                coursePosPoints[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.courseY.ColName].ToString());
                coursePosPoints[j].Z = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.courseZ.ColName].ToString());
                coursePosPoints[j].W = 0;
            }

            ComputeBuffer<real4> coursePosPointsBuffer;
            if (useNativeCPU)
            {
                GCHandle coursePosPointsPtr = GCHandle.Alloc(coursePosPoints, GCHandleType.Pinned);
                ptrHolder.coursePosPointsPtr = coursePosPointsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(coursePosPointsPtr);
            }
            else
            {
                coursePosPointsBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, coursePosPoints);
                kernel.SetMemoryArgument(kernelArgCount, coursePosPointsBuffer, true);
                kernelArgCount++;
            }

            //__global const real* coursePosLinear
            real[] coursePosLinear = new real[(size_array)numSimPoints];
            coursePosLinear[0] = 0;
            for (int j = 1; j < coursePosLinear.Length; j++)
            {
                coursePosLinear[j] = coursePosLinear[j - 1] + (real)Math.Sqrt(Math.Pow(coursePosPoints[j].X - coursePosPoints[j - 1].X, 2.0) +
                    Math.Pow(coursePosPoints[j].Y - coursePosPoints[j - 1].Y, 2.0) +
                    Math.Pow(coursePosPoints[j].Z - coursePosPoints[j - 1].Z, 2.0));

                if (Math.Abs(coursePosLinear[j] - coursePosLinear[j - 1]) < DataConstants.SMALL_REAL)
                    throw new Exception("Course position points are too close together at index " + j);
            }
            coursePosLinearMax = coursePosLinear[coursePosLinear.Length - 1];

            ComputeBuffer<real> coursePosLinearBuffer;
            if (useNativeCPU)
            {
                GCHandle coursePosLinearPtr = GCHandle.Alloc(coursePosLinear, GCHandleType.Pinned);
                ptrHolder.coursePosLinearPtr = coursePosLinearPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(coursePosLinearPtr);                
            }
            else
            {
                coursePosLinearBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, coursePosLinear);
                kernel.SetMemoryArgument(kernelArgCount, coursePosLinearBuffer, true);
                kernelArgCount++;

                //const real coursePosLinearMax
                kernel.SetValueArgument<real>(kernelArgCount, coursePosLinearMax);
                kernelArgCount++;
            }

            //__global const real4* coursePosDirection
            real4[] coursePosDirection = new real4[(size_array)numSimPoints];
            for (int j = 0; j < coursePosDirection.Length - 1; j++)
            {
                //real4 vectDiff = coursePosPoints[j + 1] - coursePosPoints[j];
                real4 vectDiff;
                vectDiff.X = coursePosPoints[j + 1].X - coursePosPoints[j].X;
                vectDiff.Y = coursePosPoints[j + 1].Y - coursePosPoints[j].Y;
                vectDiff.Z = coursePosPoints[j + 1].Z - coursePosPoints[j].Z;
                vectDiff.W = coursePosPoints[j + 1].W - coursePosPoints[j].W;
                coursePosDirection[j] = VectorMath.Normalize(vectDiff);
            }
            //coursePosDirection[coursePosDirection.Length - 1] = 1 * coursePosDirection[coursePosDirection.Length - 2];
            coursePosDirection[coursePosDirection.Length - 1].X = 1 * coursePosDirection[coursePosDirection.Length - 2].X;
            coursePosDirection[coursePosDirection.Length - 1].Y = 1 * coursePosDirection[coursePosDirection.Length - 2].Y;
            coursePosDirection[coursePosDirection.Length - 1].Z = 1 * coursePosDirection[coursePosDirection.Length - 2].Z;
            coursePosDirection[coursePosDirection.Length - 1].W = 1 * coursePosDirection[coursePosDirection.Length - 2].W;

            ComputeBuffer<real4> coursePosDirectionBuffer;
            if (useNativeCPU)
            {
                GCHandle coursePosDirectionPtr = GCHandle.Alloc(coursePosDirection, GCHandleType.Pinned);
                ptrHolder.coursePosDirectionPtr = coursePosDirectionPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(coursePosDirectionPtr);
            }
            else
            {
                coursePosDirectionBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, coursePosDirection);
                kernel.SetMemoryArgument(kernelArgCount, coursePosDirectionBuffer, true);
                kernelArgCount++;
            }

            //__global const real4* coursePosInfo
            real4[] coursePosInfo = new real4[(size_array)numSimPoints];
            for (int j = 0; j < coursePosInfo.Length; j++)
            {
                if (j == coursePosInfo.Length - 1)
                    coursePosInfo[j].X = coursePosInfo[j - 1].X;
                else
                    coursePosInfo[j].X = VectorMath.Grade(coursePosDirection[j]);

                coursePosInfo[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.crr.ColName].ToString());
                coursePosInfo[j].Z = 0; //DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.gravityCoeff.ColName].ToString());
                coursePosInfo[j].W = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.speedLimitHigh.ColName].ToString()); ;
            }

            ComputeBuffer<real4> coursePosInfoBuffer;
            if (useNativeCPU)
            {
                GCHandle coursePosInfoPtr = GCHandle.Alloc(coursePosInfo, GCHandleType.Pinned);
                ptrHolder.coursePosInfoPtr = coursePosInfoPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(coursePosInfoPtr);
            }
            else
            {
                coursePosInfoBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, coursePosInfo);
                kernel.SetMemoryArgument(kernelArgCount, coursePosInfoBuffer, true);
                kernelArgCount++;
            }

            //__global const real4* riderProp
            real gravityConst = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.gravityCoeff.ColName].ToString());
            real diameterFrontWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.frontWheelDiameter.ColName].ToString());
            real diameterRearWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.rearWheelDiameter.ColName].ToString());
            real diameterSqFrontWheel = (real)Math.Pow(diameterFrontWheel, 2.0);
            real diameterSqRearWheel = (real)Math.Pow(diameterRearWheel, 2.0);
            real rotInertiaFrontWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelRotInertia.TableName].Rows[0][WheelRotInertiaColNames.frontWheelRotInertia.ColName].ToString());
            real rotInertiaRearWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelRotInertia.TableName].Rows[0][WheelRotInertiaColNames.rearWheelRotInertia.ColName].ToString());

            real4[] riderProp = new real4[(size_array)numSimPoints];
            real massTotal;
            for (int j = 0; j < riderProp.Length; j++)
            {
                riderProp[j].X = 0.5f * DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.generalCdA.ColName].ToString());   // 0.5*CdA
                massTotal = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.massOfRider.ColName].ToString())
                    + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.massFrontWheel.ColName].ToString())
                    + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.massRearWheel.ColName].ToString())
                    + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.massBikeWithoutWheels.ColName].ToString());
                riderProp[j].Y = massTotal * gravityConst;     // weightTotal
                riderProp[j].Z = 1 / (massTotal + 4 * rotInertiaFrontWheel / diameterSqFrontWheel + 4 * rotInertiaRearWheel / diameterSqRearWheel);     // inverseMassAndRotInertiaTotal
                riderProp[j].W = 0;
            }

            ComputeBuffer<real4> riderPropBuffer;
            if (useNativeCPU)
            {
                GCHandle riderPropPtr = GCHandle.Alloc(riderProp, GCHandleType.Pinned);
                ptrHolder.riderPropPtr = riderPropPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(riderPropPtr);
            }
            else
            {
                riderPropBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, riderProp);
                kernel.SetMemoryArgument(kernelArgCount, riderPropBuffer, true);
                kernelArgCount++;
            }

            //__global const real4* wind
            bool stillWind = true;
            real4[] wind = new real4[(size_array)numSimPoints];
            for (int j = 0; j < wind.Length; j++)
            {
                wind[j].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.windX.ColName].ToString());
                wind[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.windY.ColName].ToString());
                wind[j].Z = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.windZ.ColName].ToString());

                if (wind[j].X != 0 || wind[j].Y != 0 || wind[j].Z != 0)
                    stillWind = false;

                wind[j].W = 0;
            }

            ComputeBuffer<real4> windBuffer;
            if (useNativeCPU)
            {
                GCHandle windPtr = GCHandle.Alloc(wind, GCHandleType.Pinned);
                ptrHolder.windPtr = windPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(windPtr);
            }
            else
            {
                windBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, wind);
                kernel.SetMemoryArgument(kernelArgCount, windBuffer, true);
                kernelArgCount++;
            }

            //__global const real4* climate
            real4[] climate = new real4[(size_array)numSimPoints];
            for (int j = 0; j < climate.Length; j++)
            {
                climate[j].X = 0;
                climate[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.airTemperature.ColName].ToString());
                climate[j].Z = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.dewPointTemperature.ColName].ToString());
                climate[j].W = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[j][CourseColNames.airPressure.ColName].ToString()); ;
            }
            AirDensity.Calculate(climate, coursePosLinear, coursePosPoints);

            ComputeBuffer<real4> climateBuffer;
            if (useNativeCPU)
            {
                GCHandle climatePtr = GCHandle.Alloc(climate, GCHandleType.Pinned);
                ptrHolder.climatePtr = climatePtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(climatePtr);
            }
            else
            {
                climateBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, climate);
                kernel.SetMemoryArgument(kernelArgCount, climateBuffer, true);
                kernelArgCount++;
            }

            #endregion

            #region Wheel Drag Coeff

            //__global const real* dragCoeffWheelYawAngles
            //__global const real2* dragCoeffWheels     // premultiplied with diameter and shielding factor
            real[] dragCoeffWheelYawAngles = new real[ocData.Tables[DataFiles.wheelDragCoeff.TableName].Rows.Count];
            real2[] dragCoeffWheels = new real2[dragCoeffWheelYawAngles.Length];
            for (int j = 0; j < dragCoeffWheelYawAngles.Length; j++)
            {
                dragCoeffWheelYawAngles[j] = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelDragCoeff.TableName].Rows[j][WheelDragCoeffColNames.yawAngle.ColName].ToString());
                dragCoeffWheels[j].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelDragCoeff.TableName].Rows[j][WheelDragCoeffColNames.frontWheelDragCoeff.ColName].ToString())
                    * diameterSqFrontWheel;
                dragCoeffWheels[j].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelDragCoeff.TableName].Rows[j][WheelDragCoeffColNames.rearWheelDragCoeff.ColName].ToString())
                    * diameterSqRearWheel * (1 - DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.rearWheelShieldingFactor.ColName].ToString()));
            }

            ComputeBuffer<real> dragCoeffWheelYawAnglesBuffer;
            ComputeBuffer<real2> dragCoeffWheelsBuffer;

            if (useNativeCPU)
            {
                GCHandle dragCoeffWheelYawAnglesPtr = GCHandle.Alloc(dragCoeffWheelYawAngles, GCHandleType.Pinned);
                ptrHolder.dragCoeffWheelYawAnglesPtr = dragCoeffWheelYawAnglesPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(dragCoeffWheelYawAnglesPtr);

                GCHandle dragCoeffWheelsPtr = GCHandle.Alloc(dragCoeffWheels, GCHandleType.Pinned);
                ptrHolder.dragCoeffWheelsPtr = dragCoeffWheelsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(dragCoeffWheelsPtr);

                //const sizeT dragCoeffWheelsNumPts
                dragCoeffWheelsNumPts = (sizeT)dragCoeffWheelYawAngles.Length;
            }
            else
            {
                dragCoeffWheelYawAnglesBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, dragCoeffWheelYawAngles);
                kernel.SetMemoryArgument(kernelArgCount, dragCoeffWheelYawAnglesBuffer, true);
                kernelArgCount++;

                dragCoeffWheelsBuffer = new ComputeBuffer<real2>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, dragCoeffWheels);
                kernel.SetMemoryArgument(kernelArgCount, dragCoeffWheelsBuffer, true);
                kernelArgCount++;

                //const sizeT dragCoeffWheelsNumPts
                dragCoeffWheelsNumPts = (sizeT)dragCoeffWheelYawAngles.Length;
                kernel.SetValueArgument<sizeT>(kernelArgCount, dragCoeffWheelsNumPts);
                kernelArgCount++;
            }

            #endregion

            #region Model Info

            //__global const ModelInfo* modelInfo
            ModelInfo[] modelInfo = new ModelInfo[1];

            modelInfo[0].CdA = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[0][CourseColNames.generalCdA.ColName].ToString());
            modelInfo[0].halfCdA = modelInfo[0].CdA / 2;

            modelInfo[0].diameterFrontWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.frontWheelDiameter.ColName].ToString());
            modelInfo[0].diameterRearWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.rearWheelDiameter.ColName].ToString());
            modelInfo[0].diameterSqFrontWheel = (real)Math.Pow(modelInfo[0].diameterFrontWheel, 2.0);
            modelInfo[0].diameterSqRearWheel = (real)Math.Pow(modelInfo[0].diameterRearWheel, 2.0);
            modelInfo[0].driveTrainEfficiency = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.driveTrainEfficiency.ColName].ToString());

            modelInfo[0].rotInertiaFrontWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelRotInertia.TableName].Rows[0][WheelRotInertiaColNames.frontWheelRotInertia.ColName].ToString());
            modelInfo[0].rotInertiaRearWheel = DataConversion.ConvertToReal(ocData.Tables[DataFiles.wheelRotInertia.TableName].Rows[0][WheelRotInertiaColNames.rearWheelRotInertia.ColName].ToString());

            modelInfo[0].massTotal = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[0][CourseColNames.massOfRider.ColName].ToString())
                + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[0][CourseColNames.massFrontWheel.ColName].ToString())
                + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[0][CourseColNames.massRearWheel.ColName].ToString())
                + DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[0][CourseColNames.massBikeWithoutWheels.ColName].ToString());
            modelInfo[0].weightTotal = modelInfo[0].massTotal *
                DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.gravityCoeff.ColName].ToString());
            modelInfo[0].massAndRotInertiaTotal = modelInfo[0].massTotal + 4 * modelInfo[0].rotInertiaFrontWheel / modelInfo[0].diameterSqFrontWheel
                + 4 * modelInfo[0].rotInertiaRearWheel / modelInfo[0].diameterSqRearWheel;
            modelInfo[0].inverseMassAndRotInertiaTotal = 1 / modelInfo[0].massAndRotInertiaTotal;

            modelInfo[0].minSpeed = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.minSpeed.ColName].ToString());
            modelInfo[0].initialSpeed = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.initialSpeed.ColName].ToString());
            modelInfo[0].rearWheelShieldingFactor = DataConversion.ConvertToReal(ocData.Tables[DataFiles.miscModelData.TableName].Rows[0][MiscModelDataColNames.rearWheelShieldingFactor.ColName].ToString());
            modelInfo[0].oneMinusRearWheelShieldingFactor = 1 - modelInfo[0].rearWheelShieldingFactor;

            modelInfo[0].stillWind = Convert.ToInt32(stillWind);

            ComputeBuffer<ModelInfo> modelInfoBuffer;
            if (useNativeCPU)
            {
                GCHandle modelInfoPtr = GCHandle.Alloc(modelInfo, GCHandleType.Pinned);
                ptrHolder.modelInfoPtr = modelInfoPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(modelInfoPtr);
            }
            else
            {
                modelInfoBuffer = new ComputeBuffer<ModelInfo>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, modelInfo);
                kernel.SetMemoryArgument(kernelArgCount, modelInfoBuffer, true);
                kernelArgCount++;
            }

            #endregion

            #region Integration Constants + RNG Seeds

            //__global const real4* iConstants
            // (Tol {s0}, Hmin {s1}, Hmax {s2}, Hstart {s3})
            real4[] iConstants = new real4[1];
            iConstants[0].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.integrationGlobalTol.ColName].ToString());
            iConstants[0].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.integrationMinStepSize.ColName].ToString());
            iConstants[0].Z = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.integrationMaxStepSize.ColName].ToString());
            iConstants[0].W = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.integrationInitialStepSize.ColName].ToString());

            ComputeBuffer<real4> iConstantsBuffer;
            if (useNativeCPU)
            {
                GCHandle iConstantsPtr = GCHandle.Alloc(iConstants, GCHandleType.Pinned);
                ptrHolder.iConstantsPtr = iConstantsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(iConstantsPtr);

                //__global uint4* randNumGenSeeds
                GCHandle randNumGenSeedsPtr = GCHandle.Alloc(solveData.randNumGenSeeds, GCHandleType.Pinned);
                ptrHolder.randNumGenSeedsPtr = randNumGenSeedsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(randNumGenSeedsPtr);
            }
            else
            {
                iConstantsBuffer = new ComputeBuffer<real4>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, iConstants);
                kernel.SetMemoryArgument(kernelArgCount, iConstantsBuffer, true);
                kernelArgCount++;

                //__global uint4* randNumGenSeeds
                randNumGenSeedsBuffer = new ComputeBuffer<uint4>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.randNumGenSeeds);
                kernel.SetMemoryArgument(kernelArgCount, randNumGenSeedsBuffer, true);
                kernelArgCount++;
            }


            #endregion

            #region Solver Info
            progressUpdateFrequency = Convert.ToUInt64(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.progressUpdateFrequency.ColName].ToString());
            intermedSaveFrequency = Convert.ToUInt64(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.intermedSaveFrequency.ColName].ToString());
            keepAllIntermed = Convert.ToBoolean(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.keepAllIntermed.ColName].ToString());

            //__global const SolverInfo* solverInfo
            SolverInfo[] solverInfo = new SolverInfo[1];

            solverInfo[0].diffEvoFLimits.X = 0.01f;
            solverInfo[0].diffEvoFLimits.Y = 2.0f;

            solverInfo[0].diffEvoCrLimits.X = 0.90f;
            solverInfo[0].diffEvoCrLimits.Y = 1.0f;

            solverInfo[0].diffEvoSrLimits.X = 0.01f;
            solverInfo[0].diffEvoSrLimits.Y = 1.0f;

            solverInfo[0].bwCutOffF.X = 0.05f;
            solverInfo[0].bwCutOffF.Y = 0.2f;

            solverInfo[0].heuristicSeedIndexLow = (sizeT)1;
            solverInfo[0].heuristicSeedIndexHigh = (sizeT)((size_array)solverInfo[0].heuristicSeedIndexLow - 1 +
                (size_array)DataConversion.ConvertTosizeT(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.maxHeuristicSeeds.ColName].ToString()));

            solverInfo[0].maxGensToRun = Convert.ToUInt64(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.maxGensToRun.ColName].ToString());

            solverInfo[0].numIndividMigrate = DataConversion.ConvertTosizeT(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.migrationSize.ColName].ToString());
            solverInfo[0].gensPerMigration = Convert.ToUInt64(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.gensBetweenMigrations.ColName].ToString());

            solverInfo[0].convergenenceGenerations = Convert.ToUInt64(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.convergenenceGenerations.ColName].ToString());
            solverInfo[0].convergenceTolerance = DataConversion.ConvertToReal(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.convergenceTolerance.ColName].ToString());

            solveData.solverVars[0].maxTime = DataConversion.ConvertToReal(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.maxTimeExpected.ColName].ToString());
            solveData.solverVars[0].raceTimeMeasureStartIndex = DataConversion.ConvertTosizeT(ocData.Tables[DataFiles.integrationParams.TableName].Rows[0][IntegrationParamsColNames.raceTimeMeasureStartIndex.ColName].ToString());
            if ((size_array)solveData.solverVars[0].raceTimeMeasureStartIndex >= (size_array)numSimPoints)
                throw new Exception("Race Time Measure Start Index must be less than the number of simulation points.");

            ComputeBuffer<SolverInfo> solverInfoBuffer;
            if (useNativeCPU)
            {
                GCHandle solverInfoPtr = GCHandle.Alloc(solverInfo, GCHandleType.Pinned);
                ptrHolder.solverInfoPtr = solverInfoPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(solverInfoPtr);

                //__global SolverVars* solverVars
                GCHandle solverVarsPtr = GCHandle.Alloc(solveData.solverVars, GCHandleType.Pinned);
                ptrHolder.solverVarsPtr = solverVarsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(solverVarsPtr);
            }
            else
            {
                solverInfoBuffer = new ComputeBuffer<SolverInfo>(context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, solverInfo);
                kernel.SetMemoryArgument(kernelArgCount, solverInfoBuffer, true);
                kernelArgCount++;

                //__global SolverVars* solverVars
                solverVarsBuffer = new ComputeBuffer<SolverVars>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.solverVars);
                kernel.SetMemoryArgument(kernelArgCount, solverVarsBuffer, true);
                kernelArgCount++;
            }

            #endregion

            #region Power Metrics

            //__global const PowerMetricsInfo* powerMetricsInfo
            PowerMetricsInfo[] powerMetricsInfo = new PowerMetricsInfo[1];

#if CCAP_RATE_TRANSFORMS
            powerMetricsInfo[0].negFactor = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.negFactor.ColName].ToString());
            powerMetricsInfo[0].minNegPowerRate = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.minNegPowerRate.ColName].ToString());
            powerMetricsInfo[0].posFactor = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.posFactor.ColName].ToString());
            powerMetricsInfo[0].maxPosPowerRate = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.maxPosPowerRate.ColName].ToString());            
#endif
            powerMetricsInfo[0].powerRateCoeff = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.powerRateCoeff.ColName].ToString());
            powerMetricsInfo[0].powerRateExp = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.powerRateExp.ColName].ToString());
            powerMetricsInfo[0].powerRateExpInverse = 1 / powerMetricsInfo[0].powerRateExp;
            powerMetricsInfo[0].powerCurveExp = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.powerCurveExp.ColName].ToString());
            powerMetricsInfo[0].powerCurveExpInverse = 1 / powerMetricsInfo[0].powerCurveExp;
            powerMetricsInfo[0].ftp = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.ftp.ColName].ToString());

            powerMetricsInfo[0].gmExp = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.gmExp.ColName].ToString());
            powerMetricsInfo[0].gmExpInverse = (real)(1 / powerMetricsInfo[0].gmExp);
            powerMetricsInfo[0].gmExpPlusOne = powerMetricsInfo[0].gmExp + 1;
            powerMetricsInfo[0].gmRollingAveTime = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.gmRollingAveTime.ColName].ToString());
            powerMetricsInfo[0].gmRollingAveIsExp = Convert.ToBoolean(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.gmRollingAveIsExp.ColName].ToString()) ? 1 : 0;            
            powerMetricsInfo[0].gmSamplingInterval = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.gmSamplingInterval.ColName].ToString());

            powerMetricsInfo[0].pmType = (PMTypes)Enum.Parse(typeof(PMTypes), ocData.Tables[DataFiles.powerMetricsParams.TableName].Rows[0][PowerMetricsParamsColNames.powerMetricChoice.ColName].ToString());

            real2[] powerMetricsCriticals = new real2[ocData.Tables[DataFiles.powerMetricsCriticals.TableName].Rows.Count];
            for (int i = 0; i < powerMetricsCriticals.Length; i++)
            {
                powerMetricsCriticals[i].X = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsCriticals.TableName].Rows[i][PowerMetricsCriticalsColNames.duration.ColName].ToString());
                powerMetricsCriticals[i].Y = DataConversion.ConvertToReal(ocData.Tables[DataFiles.powerMetricsCriticals.TableName].Rows[i][PowerMetricsCriticalsColNames.maxPowerMetricVal.ColName].ToString());
            }
            powerMetricsInfo[0].numCriticals = (sizeT)powerMetricsCriticals.Length;

            ComputeBuffer<PowerMetricsInfo> powerMetricsInfoBuffer;
            ComputeBuffer<real2> powerMetricsCriticalsBuffer;

            if (useNativeCPU)
            {
                GCHandle powerMetricsInfoPtr = GCHandle.Alloc(powerMetricsInfo, GCHandleType.Pinned);
                ptrHolder.powerMetricsInfoPtr = powerMetricsInfoPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(powerMetricsInfoPtr);

                //__global const real2* powerMetricsCriticals
                GCHandle powerMetricsCriticalsPtr = GCHandle.Alloc(powerMetricsCriticals, GCHandleType.Pinned);
                ptrHolder.powerMetricsCriticalsPtr = powerMetricsCriticalsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(powerMetricsCriticalsPtr);
            }
            else
            {
                powerMetricsInfoBuffer = new ComputeBuffer<PowerMetricsInfo>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, powerMetricsInfo);
                kernel.SetMemoryArgument(kernelArgCount, powerMetricsInfoBuffer, true);
                kernelArgCount++;

                //__global real2* powerMetricsCriticals
                powerMetricsCriticalsBuffer = new ComputeBuffer<real2>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, powerMetricsCriticals);
                kernel.SetMemoryArgument(kernelArgCount, powerMetricsCriticalsBuffer, true);
                kernelArgCount++;
            }

            #endregion

            #region Temp Working Space
            //__global real* tempWorkingSpace
            real[] tempWorkingSpace = new real[(size_array)numSimPoints * (size_array)numIndividuals];
            ComputeBuffer<real> tempWorkingSpaceBuffer;
            if (useNativeCPU)
            {
                GCHandle tempWorkingSpacePtr = GCHandle.Alloc(tempWorkingSpace, GCHandleType.Pinned);
                ptrHolder.tempWorkingSpacePtr = tempWorkingSpacePtr.AddrOfPinnedObject(); 
                ptrHolder.gcHandles.Add(tempWorkingSpacePtr);
            }
            else
            {
                tempWorkingSpaceBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, tempWorkingSpace);
                kernel.SetMemoryArgument(kernelArgCount, tempWorkingSpaceBuffer, true);
                kernelArgCount++;
            }
            #endregion

            #region Results

            if (useNativeCPU)
            {
                //__global real* bestTime
                GCHandle bestTimePtr = GCHandle.Alloc(solveData.bestTime, GCHandleType.Pinned);
                ptrHolder.bestTimePtr = bestTimePtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(bestTimePtr);

                GCHandle bestPowerResultsPtr = GCHandle.Alloc(solveData.bestPowerResults, GCHandleType.Pinned);
                ptrHolder.bestPowerResultsPtr = bestPowerResultsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(bestPowerResultsPtr);

                GCHandle bestKinematicsPtr = GCHandle.Alloc(solveData.bestKinematics, GCHandleType.Pinned);
                ptrHolder.bestKinematicsPtr = bestKinematicsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(bestKinematicsPtr);

                //__global real2* fitStats
                GCHandle fitStatsPtr = GCHandle.Alloc(solveData.fitStats, GCHandleType.Pinned);
                ptrHolder.fitStatsPtr = fitStatsPtr.AddrOfPinnedObject();
                ptrHolder.gcHandles.Add(fitStatsPtr);
            }
            else
            {
                //__global real* bestTime
                bestTimeBuffer = new ComputeBuffer<real>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.bestTime);
                kernel.SetMemoryArgument(kernelArgCount, bestTimeBuffer, true);
                kernelArgCount++;

                bestPowerResultsBuffer = new ComputeBuffer<real8>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.bestPowerResults);
                kernel.SetMemoryArgument(kernelArgCount, bestPowerResultsBuffer, true);
                kernelArgCount++;

                bestKinematicsBuffer = new ComputeBuffer<real8>(context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer, solveData.bestKinematics);
                kernel.SetMemoryArgument(kernelArgCount, bestKinematicsBuffer, true);
                kernelArgCount++;

                //__global real2* fitStats
                fitStatsBuffer = new ComputeBuffer<real2>(context, ComputeMemoryFlags.WriteOnly | ComputeMemoryFlags.CopyHostPointer, solveData.fitStats);
                kernel.SetMemoryArgument(kernelArgCount, fitStatsBuffer, true);
                kernelArgCount++;
            }

            #endregion
            
            stopWatch.Reset();
            stopWatch.Start();

            if (powerOptChoice != OptimizationChoices.NoOptimization)
            {
                #region First Gen
                ulong subSeqStartGen;

                if (solveData.solverVars[0].currGen == 0)
                {
                    whichArray = 0;

                    if (useNativeCPU)
                    {
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_Initialize, whichArray);
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_EvaluateFitness, whichArray);
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_SortByFitness, whichArray);
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_IncrementParams, whichArray);
                    }
                    else
                    {
                        kernel.SetValueArgument<int>(whichArrayIndex, whichArray);

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_Initialize);
                        queue.Execute(kernel, null, new long[] { numGlobalExecutors }, null, null);

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_EvaluateFitness);
                        queue.Execute(kernel, null, new long[] { numGlobalExecutors }, null, null);

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_SortByFitness);
                        queue.Execute(kernel, null, new long[] { numGlobalExecutors }, null, null);

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_IncrementParams);
                        queue.Execute(kernel, null, new long[] { 1 }, null, null);

                        queue.Finish();
                    }

                    if (intermedSaveFrequency != 0)
                    {
                        ReadKernelResults();
                    }
                    else
                    {
                        ReadKernelResultsTime();
                    }

                    solveData.convergeGenCount = 0;
                    subSeqStartGen = 1;

                    if (intermedSaveFrequency != 0)
                    {
                        if (keepAllIntermed)
                        {
                            using (StreamWriter streamWriter = File.CreateText(Path.Combine(intermedFolderPath, "Intermed" + "0" + ".xml")))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }
                        }

                        if (intermedABFlag == 0)
                        {
                            using (StreamWriter streamWriter = File.CreateText(intermedB))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }
                            intermedABFlag = 1;
                        }
                        else
                        {
                            using (StreamWriter streamWriter = File.CreateText(intermedA))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }
                            intermedABFlag = 0;
                        }
                    }

                    if (progressUpdateFrequency != 0)
                    {

                        string titleMessage = "Compute Time".PadLeft(13) + "," + "Gen #".PadLeft(13) + "," + "Best Time [s]".PadLeft(15)
                            + "," + "Viable".PadLeft(10) + "," + "Convergence".PadLeft(18);

                        Console.SetOut(consoleOutFileWriter);
                        Console.WriteLine(titleMessage);

                        Console.SetOut(stdOutTextWriter);
                        Console.WriteLine(titleMessage);

                        outProgressMsg(stopWatch.Elapsed, 0, solveData.bestTime[0], (sizeT)solveData.fitStats[0].Y, numIndividuals, solveData.fitStats[0].X,
                            consoleOutFileWriter, stdOutTextWriter);
                    }
                }
                else
                {
                    subSeqStartGen = solveData.solverVars[0].currGen;
                    whichArray = (int)((subSeqStartGen - 1) % 2);

                    if (!useNativeCPU)
                    {
                        kernel.SetValueArgument<int>(whichArrayIndex, whichArray);
                    }
                }
                #endregion

                #region Subsequent Gens

                for (ulong i = subSeqStartGen; i < solverInfo[0].maxGensToRun; i++)
                {
                    if (useNativeCPU)
                    {
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_DiffEvo, whichArray);
                        whichArray = (int)(i % 2);

                        if ((size_array)numPopulations > 1 && i % solverInfo[0].gensPerMigration == 0)
                        {
                            OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_Migrate, whichArray);
                        }

                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_SortByFitness, whichArray);
                        OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_IncrementParams, whichArray);
                    }
                    else
                    {

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_DiffEvo);
                        queue.Execute(kernel, null, new long[] { numGlobalExecutors }, null, null);

                        whichArray = (int)(i % 2);
                        kernel.SetValueArgument<int>(whichArrayIndex, whichArray);

                        if ((size_array)numPopulations > 1 &&  i % solverInfo[0].gensPerMigration == 0)
                        {
                            kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_Migrate);
                            queue.Execute(kernel, null, new long[] { 1 }, null, null);
                        }

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_SortByFitness);
                        queue.Execute(kernel, null, new long[] { numGlobalExecutors }, null, null);

                        kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_IncrementParams);
                        queue.Execute(kernel, null, new long[] { 1 }, null, null);

                        queue.Finish();
                    }

                    if (intermedSaveFrequency != 0 && (i % intermedSaveFrequency == 0 || (i + 1) == solverInfo[0].maxGensToRun))
                    {
                        ReadKernelResults();
                    }
                    else
                    {
                        ReadKernelResultsTime();
                    }

                    real convergence = Math.Abs(solveData.fitStats[0].X - solveData.bestTime[0]);
                    if (convergence < solverInfo[0].convergenceTolerance)
                    {
                        solveData.convergeGenCount++;
                    }
                    else
                    {
                        solveData.convergeGenCount = 0;
                    }

                    // Any time exit
                    if (intermedSaveFrequency != 0 && (i % intermedSaveFrequency == 0 || (i + 1) == solverInfo[0].maxGensToRun))
                    {
                        if (keepAllIntermed)
                        {
                            using (StreamWriter streamWriter = File.CreateText(Path.Combine(intermedFolderPath, "Intermed" + i + ".xml")))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }
                        }

                        if (intermedABFlag == 0)
                        {
                            using (StreamWriter streamWriter = File.CreateText(intermedB))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }

                            intermedABFlag = 1;
                        }
                        else
                        {
                            using (StreamWriter streamWriter = File.CreateText(intermedA))
                            {
                                serializer.Serialize(streamWriter, solveData);
                            }

                            intermedABFlag = 0;
                        }

                        //consoleOutFileWriter.Flush();   // Force a write to disk. This appears to cause corruption of the intermediate files during long simulations.
                    }

                    if (progressUpdateFrequency != 0 && (i % progressUpdateFrequency == 0 || (i + 1) == solverInfo[0].maxGensToRun))
                    {
                        outProgressMsg(stopWatch.Elapsed, i, solveData.bestTime[0], (sizeT)solveData.fitStats[0].Y, numIndividuals, solveData.fitStats[0].X,
                            consoleOutFileWriter, stdOutTextWriter);
                    }

                    if (solveData.convergeGenCount >= solverInfo[0].convergenenceGenerations)
                        break;
                }

                #endregion

                if (useNativeCPU)
                {
                    OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_OutputAll, whichArray);
                }
                else
                {
                    kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_OutputAll);
                    queue.Execute(kernel, null, new long[] { 1 }, null, null);
                    queue.Finish();
                }

            }
            else
            {
                whichArray = 0;
                if (useNativeCPU)
                {
                    OCSimRun(ptrHolder, (int)SolutionMode.SolutionMode_NoOptimize, whichArray);
                }
                else
                {
                    kernel.SetValueArgument<int>(whichArrayIndex, whichArray);

                    kernel.SetValueArgument<int>(currSolutionModeIndex, (int)SolutionMode.SolutionMode_NoOptimize);
                    queue.Execute(kernel, null, new long[] { 1 }, null, null);

                    queue.Finish();
                }
            }

            stopWatch.Stop();
            ReadKernelResults();

            #region Write Results to File

            DataTable resultsPowerMetrics = ocData.Tables[DataFiles.resultsPowerMetrics.TableName];
            resultsPowerMetrics.Rows.Clear();
            resultsPowerMetrics.BeginLoadData();
            {
                DataRow dr = resultsPowerMetrics.NewRow();
                if (solveData.bestTime[solveData.bestTime.Length - 1] < solveData.solverVars[0].maxTime)
                {
                    dr[ResultsPowerMetricColNames.raceTime.ColName] = (double)solveData.bestTime[0];
                    dr[ResultsPowerMetricColNames.totalTime.ColName] = (double)solveData.bestPowerResults[0].s5;
                    dr[ResultsPowerMetricColNames.ccap.ColName] = (double)solveData.bestPowerResults[0].s2;
                    dr[ResultsPowerMetricColNames.pmCritical.ColName] = (double)solveData.bestPowerResults[0].s3;
                    dr[ResultsPowerMetricColNames.aveSpeed.ColName] = (double)(coursePosLinear[coursePosLinear.Length - 1] / solveData.bestTime[solveData.bestTime.Length - 1] * 3.6);
                    dr[ResultsPowerMetricColNames.energyUsed.ColName] = (double)solveData.bestPowerResults[0].s0;
                    dr[ResultsPowerMetricColNames.avePower.ColName] = (double)solveData.bestPowerResults[0].s1;
                    dr[ResultsPowerMetricColNames.gm.ColName] = (double)solveData.bestPowerResults[0].s4;
                }
                else
                {
                    dr[ResultsPowerMetricColNames.raceTime.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.totalTime.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.ccap.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.pmCritical.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.aveSpeed.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.energyUsed.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.avePower.ColName] = (double)float.MaxValue;
                    dr[ResultsPowerMetricColNames.gm.ColName] = (double)float.MaxValue;
                }
                resultsPowerMetrics.Rows.Add(dr);
            }
            resultsPowerMetrics.EndLoadData();

            DataTable resultsCourse = ocData.Tables[DataFiles.resultsCourse.TableName];
            resultsCourse.Rows.Clear();
            resultsCourse.BeginLoadData();
            for (int i = 0; i < (int)numSimPoints; i++)
            {
                DataRow dr = resultsCourse.NewRow();
                dr[ResultsCourseColNames.courseX.ColName] = (double)coursePosPoints[i].X;
                dr[ResultsCourseColNames.courseY.ColName] = (double)coursePosPoints[i].Y;
                dr[ResultsCourseColNames.courseZ.ColName] = (double)coursePosPoints[i].Z;

                dr[ResultsCourseColNames.linearPosition.ColName] = (double)coursePosLinear[i];

                dr[ResultsCourseColNames.windX.ColName] = (double)wind[i].X;
                dr[ResultsCourseColNames.windY.ColName] = (double)wind[i].Y;
                dr[ResultsCourseColNames.windZ.ColName] = (double)wind[i].Z;

                dr[ResultsCourseColNames.time.ColName] = (double)solveData.bestKinematics[i].s1;
                dr[ResultsCourseColNames.powerRaw.ColName] = (double)solveData.bestKinematics[i].s0;                
                dr[ResultsCourseColNames.speed.ColName] = (double)solveData.bestKinematics[i].s2;
                dr[ResultsCourseColNames.acceleration.ColName] = (double)solveData.bestKinematics[i].s3;

                dr[ResultsCourseColNames.airDensity.ColName] = (double)climate[i].X;

                resultsCourse.Rows.Add(dr);
            }
            resultsCourse.EndLoadData();

            FileOCDataV2_1.WriteOCData(ocData, true, folderPath);
            #endregion

            #region Display Results

            string finishedMessage = "\n---Finished!---\n\nBest Time [s]:" + String.Format("{0,15:0.0000}", solveData.bestTime[0] + "\n\n");
            Console.SetOut(consoleOutFileWriter);
            Console.WriteLine(finishedMessage);

            Console.SetOut(stdOutTextWriter);
            Console.WriteLine(finishedMessage);

            #endregion

            #region Cleanup

            if (consoleOutFileWriter != null)
            {
                consoleOutFileWriter.Flush();
                consoleOutFileWriter.Close();
            }

            if (ptrHolder != null && ptrHolder.gcHandles != null && ptrHolder.gcHandles.Count > 0)
            {
                for (int i = 0; i < ptrHolder.gcHandles.Count; i++)
                {
                    ptrHolder.gcHandles[i].Free();
                }
            }

            #endregion

            return (double)solveData.bestTime[0];
        }
    }
}